/*******************************************************************************
 * Copyright 2013 Comcast Cable Communications Management, LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.marshalchen.common.uimodule.freeflow.animations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;


import android.animation.Animator;
import android.animation.Animator.AnimatorListener;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.graphics.Rect;
import android.util.Log;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.animation.DecelerateInterpolator;

import com.marshalchen.common.commonUtils.logUtils.Logs;
import com.marshalchen.common.uimodule.freeflow.core.FreeFlowContainer;
import com.marshalchen.common.uimodule.freeflow.core.FreeFlowItem;
import com.marshalchen.common.uimodule.freeflow.core.LayoutChangeset;

public class DefaultLayoutAnimator implements FreeFlowLayoutAnimator {

    protected LayoutChangeset changeSet;

    public static final String TAG = "DefaultLayoutAnimator";

    /**
     * The duration of the "Appear" animation per new cell being added. Note
     * that the total time of the "Appear" animation will be based on the
     * cumulative total of this animation playing on each cell
     */
    public int newCellsAdditionAnimationDurationPerCell = 200;

    public int newCellsAdditionAnimationStartDelay = 0;

    /**
     * The duration of the "Disappearing" animation per old cell being removed.
     * Note that the total time of the "Disappearing" animation will be based on
     * the cumulative total of this animation playing on each cell being removed
     */
    public int oldCellsRemovalAnimationDuration = 200;

    public int oldCellsRemovalAnimationStartDelay = 0;

    public int cellPositionTransitionAnimationDuration = 250;

    /**
     * If set to true, this forces the animation sets to animate in the
     * following sequence: delete then add then move
     * <p/>
     * If set to false, all sets will animate in parallel
     */
    public boolean animateAllSetsSequentially = false;

    /**
     * If set to true, this forces each view in a set to animate sequentially
     * <p/>
     * If set to false, all views for a set will animate in parallel
     */
    public boolean animateIndividualCellsSequentially = false;

    protected FreeFlowContainer callback;
    protected AnimatorSet disappearingSet = null;
    protected AnimatorSet appearingSet = null;
    protected AnimatorSet movingSet = null;

    public DefaultLayoutAnimator() {
    }

    @Override
    public void cancel() {

        if (disappearingSet != null)
            disappearingSet.cancel();

        if (appearingSet != null)
            appearingSet.cancel();

        if (movingSet != null)
            movingSet.cancel();

        for (FreeFlowItem item : changeSet.getAdded()) {
            item.view.setAlpha(1f);
        }
        for (FreeFlowItem item : changeSet.getRemoved()) {
            item.view.setAlpha(1f);
        }

        mIsRunning = false;


    }

    protected boolean mIsRunning = false;

    @Override
    public void animateChanges(LayoutChangeset changeSet, final FreeFlowContainer callback) {
        this.changeSet = changeSet;
        this.callback = callback;
        cancel();

        mIsRunning = true;

        disappearingSet = null;
        appearingSet = null;
        movingSet = null;

        Comparator<FreeFlowItem> cmp = new Comparator<FreeFlowItem>() {

            @Override
            public int compare(FreeFlowItem lhs, FreeFlowItem rhs) {
                return (lhs.itemSection * 1000 + lhs.itemIndex) - (rhs.itemSection * 1000 + rhs.itemIndex);
            }
        };

        List<FreeFlowItem> removed = changeSet.getRemoved();
        if (removed.size() > 0) {
            Collections.sort(removed, cmp);
            disappearingSet = getItemsRemovedAnimation(changeSet.getRemoved());
        }

        List<FreeFlowItem> added = changeSet.getAdded();
        if (added.size() > 0) {
            Collections.sort(added, cmp);
            appearingSet = getItemsAddedAnimation(added);
        }

        if (changeSet.getMoved().size() > 0) {
            movingSet = getItemsMovedAnimation(changeSet.getMoved());
        }

        AnimatorSet all = getAnimationSequence();
        if (all == null) {
            mIsRunning = false;
            callback.onLayoutChangeAnimationsCompleted(this);
        } else {

            all.addListener(new AnimatorListener() {

                @Override
                public void onAnimationStart(Animator animation) {
                }

                @Override
                public void onAnimationRepeat(Animator animation) {
                }

                @Override
                public void onAnimationEnd(Animator animation) {
                    mIsRunning = false;
                    callback.onLayoutChangeAnimationsCompleted(DefaultLayoutAnimator.this);
                }

                @Override
                public void onAnimationCancel(Animator animation) {
                }

            });

            all.start();
        }
    }

    /**
     * The animation to run on the items being removed
     *
     * @param removed An ArrayList of <code>FreeFlowItems</code> removed
     * @return The AnimatorSet of the removed objects
     */
    protected AnimatorSet getItemsRemovedAnimation(List<FreeFlowItem> removed) {
        AnimatorSet disappearingSet = new AnimatorSet();
        ArrayList<Animator> fades = new ArrayList<Animator>();
        for (FreeFlowItem proxy : removed) {
            fades.add(ObjectAnimator.ofFloat(proxy.view, "alpha", 0));
        }
        disappearingSet.setDuration(oldCellsRemovalAnimationDuration);
        disappearingSet.setStartDelay(oldCellsRemovalAnimationStartDelay);

        if (animateIndividualCellsSequentially)
            disappearingSet.playSequentially(fades);
        else
            disappearingSet.playTogether(fades);

        return disappearingSet;
    }

    /**
     *
     */
    protected AnimatorSet getItemsAddedAnimation(List<FreeFlowItem> added) {
        AnimatorSet appearingSet = new AnimatorSet();
        ArrayList<Animator> fadeIns = new ArrayList<Animator>();
        for (FreeFlowItem proxy : added) {
            proxy.view.setAlpha(0);
            fadeIns.add(ObjectAnimator.ofFloat(proxy.view, "alpha", 1));
        }

        if (animateIndividualCellsSequentially)
            appearingSet.playSequentially(fadeIns);
        else
            appearingSet.playTogether(fadeIns);

        appearingSet.setStartDelay(newCellsAdditionAnimationStartDelay);
        appearingSet.setDuration(newCellsAdditionAnimationDurationPerCell);
        return appearingSet;
    }

    protected AnimatorSet getAnimationSequence() {

        if (disappearingSet == null && appearingSet == null && movingSet == null)
            return null;

        AnimatorSet allAnim = new AnimatorSet();

        ArrayList<Animator> all = new ArrayList<Animator>();

        if (disappearingSet != null)
            all.add(disappearingSet);

        if (appearingSet != null)
            all.add(appearingSet);

        if (movingSet != null)
            all.add(movingSet);

        if (animateAllSetsSequentially)
            allAnim.playSequentially(all);
        else
            allAnim.playTogether(all);

        return allAnim;
    }

    protected AnimatorSet getItemsMovedAnimation(List<Pair<FreeFlowItem, Rect>> moved) {

        AnimatorSet anim = new AnimatorSet();
        ArrayList<Animator> moves = new ArrayList<Animator>();
        for (Pair<FreeFlowItem, Rect> item : moved) {
            FreeFlowItem proxy = FreeFlowItem.clone(item.first);
            View v = proxy.view;

            proxy.frame.left -= callback.getViewportLeft();
            proxy.frame.top -= callback.getViewportTop();
            proxy.frame.right -= callback.getViewportLeft();
            proxy.frame.bottom -= callback.getViewportTop();

            moves.add(transitionToFrame(item.second, proxy, v));

        }

        anim.playTogether(moves);
        return anim;
    }

    // @Override
    public ValueAnimator transitionToFrame(final Rect of, final FreeFlowItem nf, final View v) {
        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
        anim.setDuration(cellPositionTransitionAnimationDuration);

        anim.addUpdateListener(new AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                try {

                    int itemWidth = of.width()
                            + (int) ((nf.frame.width() - of.width()) * animation.getAnimatedFraction());
                    int itemHeight = of.height()
                            + (int) ((nf.frame.height() - of.height()) * animation.getAnimatedFraction());
                    int widthSpec = MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY);
                    int heightSpec = MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY);

                    v.measure(widthSpec, heightSpec);

                    Rect frame = new Rect();
                    Rect nff = nf.frame;

                    frame.left = (int) (of.left + (nff.left - of.left) * animation.getAnimatedFraction());
                    frame.top = (int) (of.top + (nff.top - of.top) * animation.getAnimatedFraction());
                    frame.right = frame.left
                            + (int) (of.width() + (nff.width() - of.width()) * animation.getAnimatedFraction());
                    frame.bottom = frame.top
                            + (int) (of.height() + (nff.height() - of.height()) * animation.getAnimatedFraction());

                    v.layout(frame.left, frame.top, frame.right, frame.bottom);

                    // v.layout(nf.frame.left, nf.frame.top, nf.frame.right,
                    // nf.frame.bottom);

                    // v.setAlpha((1 - alpha) * animation.getAnimatedFraction()
                    // + alpha);
                } catch (NullPointerException e) {
                    Logs.e(TAG, "Nullpointer exception");
                    e.printStackTrace();
                    animation.cancel();
                }
            }

        });

        anim.setInterpolator(new DecelerateInterpolator(2.0f));
        return anim;
    }

    public void setDuration(int duration) {
        this.cellPositionTransitionAnimationDuration = duration;
    }

    @Override
    public LayoutChangeset getChangeSet() {
        return changeSet;
    }

    @Override
    public boolean isRunning() {
        return mIsRunning;
    }

    @Override
    public void onContainerTouchDown(MotionEvent event) {
        cancel();
    }

}
